﻿using iDetector;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using static iDetector.SdkInterface;

namespace DART.Detector.IRay.Wrapper
{
    /// <summary>
    /// 默认设置：探测器IP:192.168.8.8, 探测器连接的网卡IP:192.168.8.188
    /// </summary>
    public class IRayInvoke : IDisposable
    {
        /// <summary>
        /// 异步调度器，默认顺序调度。
        /// </summary>
        public TaskFactory TaskFactory { get; set; } = new TaskFactory(new ConcurrentExclusiveSchedulerPair().ExclusiveScheduler);

        /// <summary>
        /// 工作目录。
        /// </summary>
        public string WorkDirectory { get; private set; }

        /// <summary>
        /// 探测器id。
        /// </summary>
        public int DetectorId { get; private set; }

        /// <summary>
        /// 探测器事件。
        /// </summary>
        public event EventHandler<IRayEventArgs> Event;

        /// <summary>
        /// 将dll所在目录添加到环境变量。
        /// </summary>
        /// <param name="path"></param>
        public static string SetEnvironmentPath(string directory)
        {
            if(!Directory.Exists(directory)) return null;
            /// 获取当前环境变量
            var path = Environment.GetEnvironmentVariable("path");
            /// 如果当前路径不在环境变量中添加到环境变量
            if (!path.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
                .Contains(directory, StringComparer.OrdinalIgnoreCase))
                Environment.SetEnvironmentVariable("path", $"{directory};{path}");
            return directory;
        }

        static IRayInvoke()
        {
            if (Environment.Is64BitProcess)
                SetEnvironmentPath(Path.Combine(AppContext.BaseDirectory, "runtimes/win-x64/native"));
            else
                SetEnvironmentPath(Path.Combine(AppContext.BaseDirectory, "runtimes/win-x86/native"));
        }

        public static readonly IRayInvoke Instance = new IRayInvoke();

        /// <summary>
        /// 探测器连接的网卡IP:192.168.8.188
        /// </summary>
        private IRayInvoke()
        {
            sdkCallbackHandler = new SdkCallbackHandler(OnCallbackHandler);
            StartEventLoopAsync(eventLoopToken.Token).GetAwaiter();
        }

        #region 设备事件循环

        private readonly BlockingCollection<IRayEventArgs> eventLoopQueue = new BlockingCollection<IRayEventArgs>();
        private readonly CancellationTokenSource eventLoopToken = new CancellationTokenSource();
        private readonly SdkCallbackHandler sdkCallbackHandler;
        private EventHandler<IRayEventArgs> innerEvent;

        /// <summary>
        /// 内部事件循环，和探测器回调进行线程以及数据隔离。
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        [DebuggerHidden]
        private async Task StartEventLoopAsync(CancellationToken cancellation)
        {
            await Task.Factory.StartNew(() =>
            {
                while (!cancellation.IsCancellationRequested)
                {
                    try
                    {
                        if (eventLoopQueue.TryTake(out var args, Timeout.Infinite, cancellation))
                        { 
                            Event?.Invoke(this, args);
                        }
                    }
                    catch { /* 处理所有异常，避免跳出事件循环 */ }
                }
            },
            cancellation,
            TaskCreationOptions.LongRunning /*不占用线程池，开独立线程*/,
            TaskScheduler.Default);
        }

        private void OnCallbackHandler(int nDetectorID, int nEventID, int nEventLevel, IntPtr pszMsg,
            int nParam1, int nParam2, int nPtrParamLen, IntPtr pParam)
        {
            if (nDetectorID == DetectorId)
            {
                try
                {
                    var args = new IRayEventArgs(nDetectorID, nEventID, nEventLevel, pszMsg, nParam1, nParam2, nPtrParamLen, pParam);
                    innerEvent?.Invoke(this, args);
                    eventLoopQueue.TryAdd(args);
                }
                catch { }
            }
        }

        #endregion 设备事件循环

        /// <summary>
        /// 设置探测器工作目录，如果成功会同步设置 <see cref="DetectorId"/>。
        /// </summary>
        /// <param name="workDirectory"></param>
        /// <param name="cancellation"></param>
        /// <returns> <see langword="true"/> 初始化成功，<see langword="false"/> 已初始化 </returns>
        public async Task<bool> CreateAsync(string workDirectory, CancellationToken cancellation = default)
        {
            var tcs = new TaskCompletionSource<bool>();
            await TaskFactory.StartNew(() =>
            {
                if (DetectorId == 0)
                {
                    int id = 0;
                    tcs.TrySetExceptionIfError(Create(workDirectory, sdkCallbackHandler, ref id));
                    WorkDirectory = workDirectory;
                    DetectorId = id;
                    tcs.TrySetResult(true);
                }
                tcs.TrySetResult(false);
            }, cancellation).ConfigureAwait(false);
            return await tcs.Task.ConfigureAwait(false);
        }

        /// <summary>
        /// 连接探测器。
        /// </summary>
        /// <param name="retry">重试次数, 设备刚上电有可能连接失败(SDK问题)</param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<IRayEventArgs> ConnectAsync(int retry = 10, CancellationToken cancellation = default)
        {
            IRayEventArgs args = null;
            for (int i = 0; i < retry; i++)
            {
                try
                {
                    args = await InvokeAsync(IRayCmd.Connect, null, cancellation).ConfigureAwait(false);
                    break;
                }
                catch (IRayException ex) { if (i + 1 >= retry) throw ex; }
            }
            /// 连接成功首先读取RAM同步设备，否则立即进行后续操作有几率触发异常(SDK问题)。
            if (args != null && args.Param2 == IRayError.OK)
                await InvokeAsync(IRayCmd.ReadUserRAM, null, cancellation).ConfigureAwait(false);
            return args;
        }

        /// <summary>
        /// 批量设置Attr值。
        /// </summary>
        /// <param name="attrs"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task SetAttrsAsync(IEnumerable<KeyValuePair<string, object>> attrs, CancellationToken cancellationToken = default)
        {
            if (attrs != null)
            {
                foreach (var item in attrs)
                {
                    if (Enum.TryParse<IRayAttr>(item.Key, out var attr))
                        SetAttr(attr, new IRayVariant(item.Value));
                }
            }
            await InvokeAsync(IRayCmd.WriteUserRAM, null, cancellationToken).ConfigureAwait(false); // 写入RAM
            await InvokeAsync(IRayCmd.ReadUserRAM, null, cancellationToken).ConfigureAwait(false); // 读取RAM同步数据
        }

        /// <summary>
        /// 设置探测器曝光参数。
        /// </summary>
        /// <param name="param"></param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task SetExposureAsync(IRayModeParam param, CancellationToken cancellation = default)
        {
            /// IRay探测器模式切换很慢，先获取当前曝光参数，然后只修改需要修改的值。
            var cur = await GetExposureAsync().ConfigureAwait(false);
            var dstAppMode = param.AppMode;
            var dstCorrectOption = param.CorrectOption;
            var dstTriggerMode = param.TriggerMode;
            var dstFilter = param.Filter;
            var dstExpMode = param.ExpMode;

            /// 不同AppMode支持的CorrectOption不同，此处需要先清空校准设置。
            if (cur.AppMode != dstAppMode && cur.CorrectOption != dstCorrectOption)
            {
                await InvokeAsync(
                    IRayCmd.SetCorrectOption,
                    new IRayCmdParam[] { CorrectOption.None },
                    cancellation).ConfigureAwait(false);
                cur.CorrectOption = CorrectOption.None; // 更新当前校准模式
            }
            /// 设置采集模式
            switch (dstAppMode)
            {
                case AppMode.Contact:
                case AppMode.Magnification:
                case AppMode.Stereo:
                    if (cur.AppMode != dstAppMode || cur.ExpMode != dstExpMode || cur.Filter != dstFilter)
                    {
                        await InvokeAsync(IRayCmd.SetCurAppMode,
                            new IRayCmdParam[] { dstAppMode, dstExpMode, dstFilter },
                            cancellation).ConfigureAwait(false);
                    }
                    if (cur.TriggerMode != dstTriggerMode) // 修改触发模式
                    {
                        SetAttr(IRayAttr.UROMTriggerModeW, (int)dstTriggerMode);
                        await InvokeAsync(IRayCmd.WriteUserRAM, null, cancellation).ConfigureAwait(false);
                    }
                    break;

                case AppMode.Tomo:
                    if (cur.TriggerMode != dstTriggerMode) // 先修改触发模式，再修改AppMode（TOMO不支持Outer触发，防止从2D Outer切换到Tomo Prep）
                    {
                        SetAttr(IRayAttr.UROMTriggerModeW, (int)dstTriggerMode);
                        await InvokeAsync(IRayCmd.WriteUserRAM, null, cancellation).ConfigureAwait(false);
                    }
                    if (cur.AppMode != dstAppMode || cur.ExpMode != dstExpMode || cur.Filter != dstFilter)
                    {
                        await InvokeAsync(IRayCmd.SetCurAppMode,
                            new IRayCmdParam[] { dstAppMode, dstExpMode, dstFilter },
                            cancellation).ConfigureAwait(false);
                    }
                    break;

                default:
                    break;
            }
            /// 重设CorrectOption
            if (cur.CorrectOption != dstCorrectOption)
            {
                try
                {
                    await InvokeAsync(IRayCmd.SetCorrectOption,
                        new IRayCmdParam[] { dstCorrectOption },
                        cancellation).ConfigureAwait(false);
                }
                catch (IRayException) { /*校准文件不存在*/ }
            }
        }

        /// <summary>
        /// 设置2D主曝光的附加参数，用于2D的多帧叠加采集。
        /// </summary>
        /// <remarks>此调用仅单次有效，初始化或曝光后探测器内部重置</remarks>
        /// <param name="param"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task SetDetectorParamAsync(IRayDetectorParam param, CancellationToken cancellationToken = default)
        {
            try
            {
                await InvokeAsync(IRayCmd.ChangeParamsOfCurrentDetectorMode,
                    new IRayCmdParam[] { param.ExpWin, param.AcqType, param.PGA },
                    cancellationToken).ConfigureAwait(false);
            }
            catch (IRayException) { /*4.6.1.13035(2024/3/29)之前版本SDK不支持此功能*/ }
        }

        /// <summary>
        /// 获取当前探测器曝光模式参数
        /// </summary>
        /// <returns></returns>
        public async Task<IRayModeParam> GetExposureAsync()
        {
            return await Task.FromResult(new IRayModeParam
            {
                AppMode = (AppMode)GetAttr(IRayAttr.UROMAppMode).To<int>(),
                ExpMode = (ExpModeEx)GetAttr(IRayAttr.UROMExpMode).To<int>(),
                Filter = (Filter)GetAttr(IRayAttr.CfgCurFilter).To<int>(),
                CorrectOption = (CorrectOption)GetAttr(IRayAttr.CurrentCorrectOption).To<int>(),
                TriggerMode = (TriggerMode)GetAttr(IRayAttr.UROMTriggerMode).To<int>(),
            });
        }

        /// <summary>
        /// 等待探测器Ready
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task WaitReadyAsync(CancellationToken cancellationToken = default)
        {
            while (await GetStateAsync(cancellationToken).ConfigureAwait(false) != DetectorState.Ready)
                await Task.Delay(50, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取探测器状态
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<DetectorState> GetStateAsync(CancellationToken cancellationToken = default)
        {
            return await TaskFactory.StartNew(() => (DetectorState)GetAttr(IRayAttr.State).To<int>(), cancellationToken)
                .ConfigureAwait(false);
        }

        /// <summary>
        /// 进入校准模式，设置校准Subset。
        /// </summary>
        /// <param name="subset">如果为空，则根据当前设置自动切换</param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task SetCalibrationAsync(string subset, CancellationToken cancellation = default)
        {
            await SetExposureAsync(new IRayModeParam { AppMode = AppMode.Contact, ExpMode = ExpModeEx.Null, CorrectOption = 0 }, cancellation)
                .ConfigureAwait(false);
            await WaitReadyAsync(cancellation).ConfigureAwait(false);
            await InvokeAsync(IRayCmd.SetCaliSubset, new IRayCmdParam[] { subset }, cancellation).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取当前校准Subset
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<string> GetCalibrationAsync()
        {
            return await Task.FromResult(GetAttr(IRayAttr.CurrentSubsetGain).To<string>());
        }

        /// <summary>
        /// 开始Offset校准
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<int> StartOffsetCalibAsync(CancellationToken cancellation = default)
        {
            await InvokeAsync(IRayCmd.OffsetGeneration, null, cancellation).ConfigureAwait(false);
            return GetAttr(IRayAttr.OffsetTotalFrames).To<int>();
        }

        /// <summary>
        /// 开始Defect校准, 最后需要调用<see cref="FinishCalibAsync"/>
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<int> StartDefectCalibAsync(CancellationToken cancellation = default)
        {
            await InvokeAsync(IRayCmd.DefectInit, null, cancellation).ConfigureAwait(false);
            return GetAttr(IRayAttr.DefectTotalFrames).To<int>();
        }

        /// <summary>
        /// 获取Defect校准的期望灰度
        /// </summary>
        /// <param name="index"></param>
        /// <returns></returns>
        public IRayCalibrationParam GetDefectCalibParam(int index)
        {
            var binning = GetAttr(IRayAttr.UROMBinningMode).To<int>();
            index = Math.Min(2, index);
            var kv = new int[] { 25, 25, 38 }[index];
            if (binning == 1)
            {
                var g = new[] { 8000, 28000, 2000 }[index];
                return new IRayCalibrationParam { Kilovolt = kv, Grayscale = g };
            }
            else
            {
                var g = new[] { 2000, 35000, 12000 }[index];
                return new IRayCalibrationParam { Kilovolt = kv, Grayscale = g };
            }
        }

        /// <summary>
        /// 接受当前defect校准
        /// </summary>
        /// <param name="index"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<int> AcceptDefectCalibAsync(int index, CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.DefectSelectCurrent, new IRayCmdParam[] { index }, cancellationToken).ConfigureAwait(false);
            return GetAttr(IRayAttr.DefectValidFrames).To<int>();
        }

        /// <summary>
        /// 生成defect校准
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task GenerateDefectCalibAsync(CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.DefectGeneration, null, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 开始Gain校准, 最后需要调用<see cref="FinishCalibAsync"/>
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<int> StartGainCalibAsync(CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.GainInit, null, cancellationToken).ConfigureAwait(false);
            var subset = GetAttr(IRayAttr.CurrentSubsetGain).To<string>();
            //var mode = GetAttr(IRayAttr.CurrentSubset).To<string>();
            var gainNumber = 5;
            if (subset.Contains("StereoMain"))
                gainNumber = gainNumber * 2;
            else if (subset.Contains("TomoMain"))
                gainNumber = gainNumber * GetAttr(IRayAttr.UROMContinuCapFrameNum).To<int>();
            return gainNumber;
        }

        /// <summary>
        /// 获取Gain校准期望灰度
        /// </summary>
        /// <returns></returns>
        public IRayCalibrationParam GetGainCalibParam()
        {
            var binning = GetAttr(IRayAttr.UROMBinningMode).To<int>();
            if (binning == 1)
                return new IRayCalibrationParam { Kilovolt = 28, Grayscale = 20000 };
            else
                return new IRayCalibrationParam { Kilovolt = 28, Grayscale = 10000 };
        }

        /// <summary>
        /// 接受当前Gain校准
        /// </summary>
        /// <param name="index"></param>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task<int> AcceptGainCalibAsync(int index, CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.GainSelectCurrent, new IRayCmdParam[] { index / 5, index % 5 }, cancellationToken).ConfigureAwait(false);
            return GetAttr(IRayAttr.GainValidFrames).To<int>();
        }

        /// <summary>
        /// 生成Gain校准
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task GenerateGainCalibAsync(CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.GainGeneration, null, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 结束校准，仅 Defect 和 Gain 校准结束后需要调用
        /// </summary>
        /// <param name="cancellationToken"></param>
        /// <returns></returns>
        public async Task FinishCalibAsync(CancellationToken cancellationToken = default)
        {
            await InvokeAsync(IRayCmd.FinishGenerationProcess, null, cancellationToken).ConfigureAwait(false);
        }

        /// <summary>
        /// 获取温度
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<double> GetTemperatureAsync(CancellationToken cancellation = default)
        {
            await InvokeAsync(IRayCmd.ReadTemperature, null, cancellation).ConfigureAwait(false);
            return GetAttr(IRayAttr.RdResultT2).To<double>();
        }

        /// <summary>
        /// 获取湿度
        /// </summary>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<double> GetHumidityAsync(CancellationToken cancellation = default)
        {
            await InvokeAsync(IRayCmd.ReadHumidity, null, cancellation).ConfigureAwait(false);
            return GetAttr(IRayAttr.RdResultHumidity).To<double>();
        }

        /// <summary>
        /// 执行指定的命令
        /// </summary>
        /// <param name="cmd"></param>
        /// <param name="cmdParams"></param>
        /// <param name="cancellation"></param>
        /// <returns></returns>
        public async Task<IRayEventArgs> InvokeAsync(IRayCmd cmd, IRayCmdParam[] cmdParams = null, CancellationToken cancellation = default)
        {
            /// 使用TaskCompletionSource将基于Event的异步模式(EAP)转换为基于Task的异步模式(TPL)
            /// https://learn.microsoft.com/zh-cn/dotnet/standard/parallel-programming/how-to-wrap-eap-patterns-in-a-task
            var tcs = new TaskCompletionSource<IRayEventArgs>(TaskCreationOptions.RunContinuationsAsynchronously);
            using (var reg = cancellation.Register(() => tcs.TrySetCanceled(cancellation)))
            {
                var handler = new EventHandler<IRayEventArgs>((_this, _args) =>
                {
                    if (_args.Param1 == cmd)
                    {
                        if (_args.Param2 != IRayError.OK)
                            tcs.TrySetException(new IRayException(_args));
                        else
                            tcs.TrySetResult(_args);
                    }
                });
                innerEvent += handler; // 注册回调
                try
                {
                    await TaskFactory.StartNew(() =>
                    {
                        var param = cmdParams?.Select(_ => (iDetector.IRayCmdParam)_).ToArray();
                        var error = Invoke(DetectorId, (int)cmd, param, param?.Length ?? 0);
                        if (error != (int)IRayError.TaskPending) // 此命令不需要等待回调
                        {
                            if (error == (int)IRayError.OK) // 成功返回
                                tcs.TrySetResult(new IRayEventArgs(DetectorId, 0, 0, IntPtr.Zero, (int)cmd, error, 0, IntPtr.Zero));
                            else // 抛出异常
                                tcs.TrySetException(new IRayException(error, cmd));
                        }
                    }, cancellation).ConfigureAwait(false);
                    return await tcs.Task.ConfigureAwait(false); // 异步等待tcs
                }
                finally { innerEvent -= handler; /* 取消注册回调 */ }
            }
        }

        /// <summary>
        /// 获取所有可用属性
        /// </summary>
        /// <returns></returns>
        public IEnumerable<IRayAttr> GetAllAvailableAttrs()
        {
            var pCount = 0;
            GetAttrsCount(DetectorId, ref pCount).ThrowIfError();
            var pList = new int[pCount];
            GetAttrIDList(DetectorId, pList, pCount).ThrowIfError();
            return pList.Select(_ => (IRayAttr)_);
        }

        /// <summary>
        /// 获取属性的信息
        /// </summary>
        /// <returns></returns>
        public IRayAttrInfo GetAttrInfo(IRayAttr attr)
        {
            var info = new AttrInfo();
            SdkInterface.GetAttrInfo(DetectorId, (int)attr, ref info);
            return info;
        }

        /// <summary>
        /// 获取指定属性的值
        /// </summary>
        /// <param name="attr"></param>
        /// <returns></returns>
        public IRayVariant GetAttr(IRayAttr attr)
        {
            var result = new iDetector.IRayVariant();
            SdkInterface.GetAttr(DetectorId, (int)attr, ref result).ThrowIfError();
            return new IRayVariant(result);
        }

        /// <summary>
        /// 设置指定属性的值
        /// </summary>
        /// <param name="attr"></param>
        /// <param name="value"></param>
        public void SetAttr(IRayAttr attr, IRayVariant value)
        {
            iDetector.IRayVariant v = value;
            SdkInterface.SetAttr(DetectorId, (int)attr, ref v).ThrowIfError();
        }

        #region IDisposable模式

        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // 释放托管状态(托管对象)
                    eventLoopToken.Cancel();
                    eventLoopToken.Dispose();
                    eventLoopQueue.Dispose();
                }
                // 释放未托管的资源(未托管的对象)
                if (DetectorId != 0)
                {
                    Destroy(DetectorId);
                    DetectorId = 0;
                }
                disposedValue = true;
            }
        }

        ~IRayInvoke()
        {
            Dispose(disposing: false);
        }

        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

        #endregion IDisposable模式
    }
}